package com.kltong.inf.dubbo.autoconfig;

import java.util.Map;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeansException;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.util.CollectionUtils;

import com.alibaba.dubbo.config.ApplicationConfig;
import com.alibaba.dubbo.config.ConsumerConfig;
import com.alibaba.dubbo.config.MonitorConfig;
import com.alibaba.dubbo.config.ProtocolConfig;
import com.alibaba.dubbo.config.ProviderConfig;
import com.alibaba.dubbo.config.RegistryConfig;
import com.alibaba.dubbo.config.spring.beans.factory.annotation.ReferenceAnnotationBeanPostProcessor;
import com.alibaba.dubbo.config.spring.beans.factory.annotation.ServiceAnnotationBeanPostProcessor;
import com.kltong.inf.common.disconf.autoconfig.EnableDisconf;
import com.kltong.inf.dubbo.config.DubboProperties;
import com.kltong.inf.dubbo.config.SentinelConfig;
import com.kltong.inf.dubbo.config.SentinelProperties;

import lombok.extern.slf4j.Slf4j;

@Configuration
@Slf4j
@ConfigurationProperties(prefix = "spring.dubbo")
@EnableDisconf(scanPackages = "com.kltong.inf.dubbo.config")
public class DubboAutoConfig implements ApplicationContextAware, EnvironmentAware {

	private ApplicationContext applicationContext;

	private Environment environment;

	/**
	 * 应用配置
	 */
	@Bean
	@ConditionalOnMissingBean
	public ApplicationConfig applicationConfig() {
		ApplicationConfig config = new ApplicationConfig();
		DubboProperties properties = getProperties();
		config.setName(properties.getAppName());
		config.setOwner(properties.getAppOwner());
		config.setOrganization(properties.getAppOrg());
		log.info("Dubbo 应用 ID: {}", config.getName());
		log.info("Dubbo 服务维护人: {}", config.getOwner());
		log.info("Dubbo 服务组织: {}", config.getOrganization());

		return config;
	}

	/**
	 * 注册中心配置
	 */
	@Bean
	@ConditionalOnMissingBean
	public RegistryConfig registryConfig() {
		RegistryConfig config = new RegistryConfig();
		DubboProperties properties = getProperties();
		config.setAddress(properties.getRegistryAddress());
		config.setTimeout(properties.getRegistryTimeout());
		// 必须检查注册中心是否存活
		config.setCheck(true);

		log.info("Dubbo 注册中心: {}", config.getAddress());
		log.info("Dubbo 注册中心连接超时时间: {}", config.getTimeout());
		log.info("Dubbo 服务缓存文件: {}", config.getFile());
		log.info("Dubbo 启动时检测注册中心: {}", config.isCheck());

		return config;
	}

	/**
	 * 提供者配置
	 */
	@Bean
	@ConditionalOnMissingBean
	public ProviderConfig providerConfig() {
		ProviderConfig config = new ProviderConfig();

		config.setProtocol(protocolConfig());
		DubboProperties properties = getProperties();
		config.setTimeout(properties.getProviderTimeout());
		config.setRetries(properties.getProviderRetries());
		config.setLoadbalance(properties.getProviderLoadbalance());
		config.setDelay(-1);
		config.setPayload(properties.getProviderPayload());
		if (StringUtils.isNotBlank(properties.getProviderFilter())) {
			config.setFilter(properties.getProviderFilter());
		}
		config.setValidation("true");
		log.info("Dubbo 服务默认超时时间: {}", config.getTimeout());
		log.info("Dubbo 服务默认重试次数: {}", config.getRetries());
		log.info("Dubbo 服务负载均衡方式: {}", config.getLoadbalance());
		log.info("Dubbo 服务拦截扩展: {}", config.getFilter());
		log.info("Dubbo 服务地址: {}", config.getHost());
		log.info("Dubbo 延迟暴露时间: {}", config.getDelay() > -1 ? config.getDelay() : "容器启动后");

		return config;
	}

	/**
	 * Dubbo 协议配置
	 */
	@Bean
	@ConditionalOnMissingBean
	public ProtocolConfig protocolConfig() {
		ProtocolConfig config = new ProtocolConfig();
		config.setName("dubbo");
		DubboProperties properties = getProperties();
		if (properties.getPort() != null) {
			config.setPort(properties.getPort());
		} else {
			config.setPort(-1);
		}
		config.setThreads(properties.getDubboThreads());
		config.setSerialization(properties.getDubboSerialization());
		config.setPayload(properties.getProviderPayload());

		log.info("Dubbo ProtocolConfig:{}",config);
		log.info("Dubbo 服务序列化方式: {}", config.getSerialization());
		return config;
	}

	/**
	 * 消费者配置
	 */
	@Bean
	@ConditionalOnMissingBean
	public ConsumerConfig consumerConfig() {
		ConsumerConfig config = new ConsumerConfig();
		DubboProperties properties = getProperties();
		config.setTimeout(properties.getConsumerTimeout());
		config.setRetries(properties.getConsumerRetries());
		config.setLoadbalance(properties.getConsumerLoadbalance());
		config.setCheck(properties.isConsumerCheck());
		if (StringUtils.isNotBlank(properties.getConsumerFilter())) {
			config.setFilter(properties.getConsumerFilter());
		}

		log.info("Dubbo 调用默认超时时间: {}", config.getTimeout());
		log.info("Dubbo 调用默认重试次数: {}", config.getRetries());
		log.info("Dubbo 调用负载均衡方式: {}", config.getLoadbalance());
		log.info("Dubbo 调用默认是否开启服务检测: {}", config.isCheck());
		log.info("Dubbo 调用拦截扩展: {}", config.getFilter());

		return config;
	}

	/**
	 * 监控中心配置
	 */
	@ConditionalOnMissingBean
	@Bean
	public MonitorConfig monitorConfig() {
		MonitorConfig config = new MonitorConfig();
		config.setProtocol("registry");
		return config;
	}

	@ConditionalOnMissingBean
	@Bean(name = ReferenceAnnotationBeanPostProcessor.BEAN_NAME)
	public ReferenceAnnotationBeanPostProcessor referenceAnnotationBeanPostProcessor() {
		return new ReferenceAnnotationBeanPostProcessor();
	}

	@ConditionalOnMissingBean
	@Bean
	public ServiceAnnotationBeanPostProcessor serviceAnnotationBeanPostProcessor() {
		String packageToScan = this.environment.getProperty("spring.dubbo.packageToScan");
		if (StringUtils.isEmpty(packageToScan)) {
			// Class<?> bootClass = deduceMainApplicationClass();
			Map<String, Object> bootBeans = this.applicationContext.getBeansWithAnnotation(SpringBootApplication.class);
			if (!CollectionUtils.isEmpty(bootBeans)) {
				Class<?> bootClass = bootBeans.entrySet().iterator().next().getValue().getClass();
				packageToScan = bootClass.getPackage().getName();
			}
		}

		String[] packageToScans = StringUtils.split(packageToScan, ",");

		return new ServiceAnnotationBeanPostProcessor(packageToScans);
	}

	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		this.applicationContext = applicationContext;
	}

	@Bean
	DubboProperties dubboProperties() {
		return new DubboProperties();
	}

	private DubboProperties getProperties() {
		return this.applicationContext.getBean(DubboProperties.class);
	}

	@Override
	public void setEnvironment(Environment environment) {
		this.environment = environment;
	}

	@Bean
	SentinelProperties sentinelProperties() {
		return new SentinelProperties();
	}

	@Bean
	SentinelConfig sentinelConfig() {
		SentinelProperties sentinelProperties = this.applicationContext.getBean(SentinelProperties.class);
		SentinelConfig sentinelConfig = new SentinelConfig();
		sentinelConfig.setSentinelProperties(sentinelProperties);
		return sentinelConfig;
	}

}
